package com.bing.common.aspect;

import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.bing.common.annotation.DLog;
import com.bing.common.constant.CommonConstant;
import com.bing.common.properties.LogProperties;
import com.bing.common.util.context.HttpServletRequestUtil;
import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @Description 请求日志切面
 * @Author Bing
 * @Date 2024/10/15 8:47
 */
@Slf4j
@Aspect
@Component
@Order(1000)
@ConditionalOnProperty(name = "config.requestLog.enable", havingValue = "true")
public class RequestLogAspect {

    @Resource
    private LogProperties logProperties;

    @Pointcut("execution(public * com.heakey.system.controller.*.*.*Controller.*(..))")//切入点描述
    public void requestLog() {
    }
    
    @Before("requestLog()")
    public void before(JoinPoint joinPoint){
        try {
            if(logProperties.getReqLength().equals(CommonConstant.NUM_ZERO)){
                return;
            }
            MethodSignature signature = (MethodSignature)joinPoint.getSignature();
            if(isDlog(signature.getMethod())){
                return;
            }
            String methodName = getMethodName(joinPoint);

            //获取参数值
            Object[] args = joinPoint.getArgs();
            //获取参数名称
            String[] parameterNames = signature.getParameterNames();
            Map<String, Object> paramMap = parseParam(args, parameterNames);
            String logStr = StrUtil.blankToDefault(JSONObject.toJSONString(paramMap), StringUtils.EMPTY);
            logStr = logProperties.getReqLength() > 0 ? StrUtil.sub(logStr,0,logProperties.getReqLength()) : logStr;
            log.info(getUserAgent()+"==> 方法:["+methodName+"] 入参：{}", logStr);
        }catch (Exception e){
            log.error("requestLog日志切面before异常",e);
        }
    }

    @AfterReturning(value = "requestLog()", returning = "result")
    public void after(JoinPoint joinPoint,Object result){
        try {
            if(Objects.isNull(result)){
                return;
            }
            boolean isParse = isParse(result);
            if(!isParse){
                return;
            }
            if(logProperties.getRespLength().equals(CommonConstant.NUM_ZERO)){
                return;
            }
            MethodSignature signature = (MethodSignature)joinPoint.getSignature();
            if(isDlog(signature.getMethod())){
                return;
            }
            String methodName = getMethodName(joinPoint);
            String logStr = StrUtil.blankToDefault(JSONObject.toJSONString(result), StringUtils.EMPTY);
            logStr = logProperties.getRespLength() > 0 ? StrUtil.sub(logStr,0,logProperties.getRespLength()) : logStr;
            log.info("==> 方法:["+methodName+"] 出参：{}", logStr);
        }catch (Exception e){
            log.error("requestLog日志切面after异常",e);
        }
    }

    /**
     * 获取UserAgent
     * @return
     */
    private String getUserAgent(){
        HttpServletRequest request = HttpServletRequestUtil.getRequest();
        if(Objects.isNull(request)){
            return StringUtils.EMPTY;
        }
        String userAgent = request.getHeader(CommonConstant.USER_AGENT_HEADER);
        if(StrUtil.isBlank(userAgent)){
            return StringUtils.EMPTY;
        }
        return String.format("user-agent : %s ",userAgent);
    }

    /**
     * 获取方法名
     * @param joinPoint
     * @return
     */
    private String getMethodName(JoinPoint joinPoint){
        String className = ClassUtil.getClassName(joinPoint.getTarget().getClass(), true);
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        String methodName = new StringBuffer(className)
                .append(".")
                .append(signature.getMethod().getName())
                .append("()").toString();
        return methodName;
    }

    /**
     * 判断是否需要记录日志
     * @param method
     * @return
     */
    private boolean isDlog(Method method){
        DLog dLog = method.getAnnotation(DLog.class);
        return ObjectUtil.isNotNull(dLog);
    }

    /**
     * 解析参数
     * @param args
     * @param parameterNames
     * @return
     */
    private Map<String, Object> parseParam(Object[] args,String[] parameterNames){
        if(Objects.isNull(args) || Objects.isNull(parameterNames)){
            return new HashMap<>();
        }
        if(args.length != parameterNames.length){
            return new HashMap<>();
        }
        //参数值和参数名称是顺序是一致的
        Map<String, Object> map = new LinkedHashMap<>();
        for (int i = 0; i < parameterNames.length; i++) {
            String parameterName = parameterNames[i];
            Object arg = args[i];
            if (Objects.isNull(arg)) {
                continue;
            }
            boolean isParse = isParse(arg);
            if(!isParse){
                continue;
            }
            map.put(parameterName, arg);
        }
        return map;
    }

    private boolean isParse(Object obj){
        if(obj instanceof HttpServletResponse){
            return false;
        }
        if(obj instanceof HttpServletRequest){
            return false;
        }
        if(obj instanceof MultipartFile){
            return false;
        }
        Package curPackage = obj.getClass().getPackage();
        if(curPackage.getName().startsWith("javax.servlet") || curPackage.getName().startsWith("org.springframework.http")){
            return false;
        }
        return true;
    }
}